package ibxm;

import java.io.*;

public class ProTracker {
    public static boolean is_mod(byte[] header_1084_bytes) {
        boolean is_mod;
        is_mod = false;
        if (calculate_num_channels(header_1084_bytes) > 0) {
            is_mod = true;
        }
        return is_mod;
    }

    public static Module load_mod(byte[] header_1084_bytes, DataInput data_input) throws IOException {
        int num_channels, channel_idx, panning;
        int sequence_length, restart_idx, sequence_idx;
        int num_patterns, pattern_idx, instrument_idx;
        Module module;
        num_channels = calculate_num_channels(header_1084_bytes);
        if (num_channels < 1) {
            throw new IllegalArgumentException("ProTracker: Unrecognised module format!");
        }
        module = new Module();
        module.song_title = ascii_text(header_1084_bytes, 0, 20);
        module.pal = (num_channels == 4);
        module.global_volume = 64;
        module.channel_gain = IBXM.FP_ONE * 3 / 8;
        module.default_speed = 6;
        module.default_tempo = 125;
        module.set_num_channels(num_channels);
        for (channel_idx = 0; channel_idx < num_channels; channel_idx++) {
            panning = 64;
            if ((channel_idx & 0x03) == 0x01 || (channel_idx & 0x03) == 0x02) {
                panning = 192;
            }
            module.set_initial_panning(channel_idx, panning);
        }
        sequence_length = header_1084_bytes[950] & 0x7F;
        restart_idx = header_1084_bytes[951] & 0x7F;
        if (restart_idx >= sequence_length) {
            restart_idx = 0;
        }
        module.restart_sequence_index = restart_idx;
        module.set_sequence_length(sequence_length);
        for (sequence_idx = 0; sequence_idx < sequence_length; sequence_idx++) {
            module.set_sequence(sequence_idx, header_1084_bytes[952 + sequence_idx] & 0x7F);
        }
        num_patterns = calculate_num_patterns(header_1084_bytes);
        module.set_num_patterns(num_patterns);
        for (pattern_idx = 0; pattern_idx < num_patterns; pattern_idx++) {
            module.set_pattern(pattern_idx, read_mod_pattern(data_input, num_channels));
        }
        module.set_num_instruments(31);
        for (instrument_idx = 1; instrument_idx <= 31; instrument_idx++) {
            module.set_instrument(instrument_idx, read_mod_instrument(header_1084_bytes, instrument_idx, data_input));
        }
        return module;
    }

    private static int calculate_num_patterns(byte[] module_header) {
        int num_patterns, order_entry, pattern_idx;
        num_patterns = 0;
        for (pattern_idx = 0; pattern_idx < 128; pattern_idx++) {
            order_entry = module_header[952 + pattern_idx] & 0x7F;
            if (order_entry >= num_patterns) {
                num_patterns = order_entry + 1;
            }
        }
        return num_patterns;
    }

    private static int calculate_num_channels(byte[] module_header) {
        int num_channels;
        switch ((module_header[1082] << 8) | module_header[1083]) {
            case 0x4b2e: /* M.K. */
            case 0x4b21: /* M!K! */
            case 0x542e: /* N.T. */
            case 0x5434: /* FLT4 */
                num_channels = 4;
                break;
            case 0x484e: /* xCHN */
                num_channels = module_header[1080] - 48;
                break;
            case 0x4348: /* xxCH */
                num_channels = ((module_header[1080] - 48) * 10) + (module_header[1081] - 48);
                break;
            default:
                /* Not recognised. */
                num_channels = 0;
                break;
        }
        return num_channels;
    }

    private static Pattern read_mod_pattern(DataInput data_input, int num_channels) throws IOException {
        int input_idx, output_idx;
        int period, instrument, effect, effect_param;
        Pattern pattern;
        byte[] input_pattern_data, output_pattern_data;
        pattern = new Pattern();
        pattern.num_rows = 64;
        input_pattern_data = new byte[64 * num_channels * 4];
        output_pattern_data = new byte[64 * num_channels * 5];
        data_input.readFully(input_pattern_data);
        input_idx = 0;
        output_idx = 0;
        while (input_idx < input_pattern_data.length) {
            period = (input_pattern_data[input_idx] & 0x0F) << 8;
            period = period | (input_pattern_data[input_idx + 1] & 0xFF);
            output_pattern_data[output_idx] = to_key(period);
            instrument = input_pattern_data[input_idx] & 0x10;
            instrument = instrument | ((input_pattern_data[input_idx + 2] & 0xF0) >> 4);
            output_pattern_data[output_idx + 1] = (byte) instrument;
            effect = input_pattern_data[input_idx + 2] & 0x0F;
            effect_param = input_pattern_data[input_idx + 3] & 0xFF;
            if (effect == 0x01 && effect_param == 0) {
                /* Portamento up of zero has no effect. */
                effect = 0;
            }
            if (effect == 0x02 && effect_param == 0) {
                /* Portamento down of zero has no effect. */
                effect = 0;
            }
            if (effect == 0x08 && num_channels == 4) {
                /* Some Amiga mods use effect 0x08 for reasons other than panning.*/
                effect = 0;
                effect_param = 0;
            }
            if (effect == 0x0A && effect_param == 0) {
                /* Volume slide of zero has no effect.*/
                effect = 0;
            }
            if (effect == 0x05 && effect_param == 0) {
                /* Porta + Volume slide of zero has no effect.*/
                effect = 0x03;
            }
            if (effect == 0x06 && effect_param == 0) {
                /* Vibrato + Volume slide of zero has no effect.*/
                effect = 0x04;
            }
            output_pattern_data[output_idx + 3] = (byte) effect;
            output_pattern_data[output_idx + 4] = (byte) effect_param;
            input_idx += 4;
            output_idx += 5;
        }
        pattern.set_pattern_data(output_pattern_data);
        return pattern;
    }

    private static Instrument read_mod_instrument(byte[] mod_header, int idx, DataInput data_input) throws IOException {
        int header_offset, sample_data_length;
        int loop_start, loop_length, sample_idx, fine_tune;
        Instrument instrument;
        Sample sample;
        byte[] raw_sample_data;
        short[] sample_data;
        header_offset = (idx - 1) * 30 + 20;
        instrument = new Instrument();
        instrument.name = ascii_text(mod_header, header_offset, 22);
        sample = new Sample();
        sample_data_length = unsigned_short_be(mod_header, header_offset + 22) << 1;
        fine_tune = mod_header[header_offset + 24] & 0x0F;
        if (fine_tune > 7) {
            fine_tune -= 16;
        }
        sample.transpose = (fine_tune << IBXM.FP_SHIFT) / 96;
        sample.volume = mod_header[header_offset + 25] & 0x7F;
        loop_start = unsigned_short_be(mod_header, header_offset + 26) << 1;
        loop_length = unsigned_short_be(mod_header, header_offset + 28) << 1;
        if (loop_length < 4) {
            loop_length = 0;
        }
        raw_sample_data = new byte[sample_data_length];
        sample_data = new short[sample_data_length];
        try {
            data_input.readFully(raw_sample_data);
        } catch (EOFException e) {
            System.out.println("ProTracker: Instrument " + idx + " has samples missing.");
        }
        for (sample_idx = 0; sample_idx < raw_sample_data.length; sample_idx++) {
            sample_data[sample_idx] = (short) (raw_sample_data[sample_idx] << 8);
        }
        sample.set_sample_data(sample_data, loop_start, loop_length, false);
        instrument.set_num_samples(1);
        instrument.set_sample(0, sample);
        return instrument;
    }

    private static byte to_key(int period) {
        int oct, key;
        if (period < 32) {
            key = 0;
        } else {
            oct = LogTable.log_2(7256) - LogTable.log_2(period);
            if (oct < 0) {
                key = 0;
            } else {
                key = oct * 12;
                key = key >> (IBXM.FP_SHIFT - 1);
                key = (key >> 1) + (key & 1);
            }
        }
        return (byte) key;
    }

    private static int unsigned_short_be(byte[] buf, int offset) {
        int value;
        value = (buf[offset] & 0xFF) << 8;
        value = value | (buf[offset + 1] & 0xFF);
        return value;
    }

    private static String ascii_text(byte[] buffer, int offset, int length) {
        int idx, chr;
        byte[] string_buffer;
        String string;
        string_buffer = new byte[length];
        for (idx = 0; idx < length; idx++) {
            chr = buffer[offset + idx];
            if (chr < 32) {
                chr = 32;
            }
            string_buffer[idx] = (byte) chr;
        }
        try {
            string = new String(string_buffer, 0, length, "ISO-8859-1");
        } catch (UnsupportedEncodingException e) {
            string = "";
        }
        return string;
    }
}

